﻿namespace OutLook.Model.Infrastructure
{
    using System;
    using System.Data.SqlTypes;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Text;
    using System.Xml;

    /// <summary>
    /// Class for data serialization
    /// </summary>
    public static class DataSerializerHelper
    {
        #region Xml serialization

        /// <summary>
        /// Serializes T type instance 
        /// </summary>
        /// <typeparam name="T">Type of instance to serialize</typeparam>
        /// <param name="obj">Instance to serialize</param>
        /// <returns>Serialized object in stream</returns>
        /// <exception cref="ArgumentNullException">If input object equals to null</exception>
        public static SqlXml XmlSerialize<T>(T obj)
        {
            if (obj == null)
                throw new ArgumentNullException("Null object");

            using (Stream memory = new MemoryStream())
            {
                DataContractSerializer dataContractSerializer = new DataContractSerializer(typeof(T));
                dataContractSerializer.WriteObject(memory, obj);
                memory.Flush();
                memory.Position = 0;

                return new SqlXml(XmlReader.Create(memory));
            }
        }

        /// <summary>
        /// Deserializes T type instance 
        /// </summary>
        /// <typeparam name="T">Type of instance to deserialize</typeparam>
        /// <param name="reader">SqlXml object (data source)</param>
        /// <returns>Decerialized T type instance</returns>
        /// <exception cref="ArgumentNullException">If input object equals to null</exception>
        public static T XmlDeserialize<T>(SqlXml reader)
        {
            if (reader == null || reader.IsNull)
                throw new ArgumentNullException("Null or Nullable Stream");

            if (reader.Value.Length < 1)
                return default(T);

            DataContractSerializer dataContractSerializer = new DataContractSerializer(typeof(T));

            return (T)dataContractSerializer.ReadObject(reader.CreateReader());
        }

        #endregion

        #region Binary serialization

        /// <summary>
        /// Serializes T type instance 
        /// </summary>
        /// <typeparam name="T">Type of instance to serialize</typeparam>
        /// <param name="value">Instance to serialize</param>
        /// <returns>SqlBytes object</returns>
        /// <exception cref="ArgumentNullException">If input object equals to null</exception>
        public static SqlBytes BinarySerialize<T>(T value)
        {
            if (value == null)
                throw new ArgumentNullException("Null object");

            using (Stream memory = new MemoryStream())
            {
                BinaryFormatter bf = new BinaryFormatter();
                bf.Serialize(memory, value);

                memory.Flush();
                memory.Position = 0;

                return new SqlBytes(memory);
            }
        }

        /// <summary>
        /// Deserializes T type instance 
        /// </summary>
        /// <typeparam name="T">Type of instance to deserialize</typeparam>
        /// <param name="value">SqlBytes object (data source)</param>
        /// <returns>Decerialized T type instance</returns>
        /// <exception cref="ArgumentNullException">If input object equals to null</exception>
        public static T BinaryDeserialize<T>(SqlBytes value)
        {
            /// Check the argument value
            if (value == null || value.IsNull)
                throw new ArgumentNullException("Null or Nullable Stream");

            if (value.Length < 1)
                return default(T);

            using (MemoryStream memory = new MemoryStream())
            {
                BinaryFormatter bf = new BinaryFormatter();
                return (T)bf.Deserialize(memory);
            }
        }

        #endregion

        #region Writer

        /// <summary>
        /// Writes serialized xml object into file
        /// </summary>
        /// <typeparam name="T">Object type</typeparam>
        /// <param name="obj">Object value</param>
        /// <param name="path">File path</param>
        /// <exception cref="ArgumentException">If input file name or directory is invalid</exception>
        /// <exception cref="ArgumentNullException">If input object equals to null</exception>
        public static void XmlWrite<T>(T obj, string path)
        {
            if (!Directory.Exists(Path.GetDirectoryName(path)))
                throw new ArgumentException("Directory doesn't exist");

            if (!Path.HasExtension(path))
                throw new ArgumentException("Invalid file name");

            if (obj == null)
                throw new ArgumentNullException("Null object");

            DataContractSerializer dataContractSerializer = new DataContractSerializer(typeof(T));
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;

            using (XmlWriter writer = XmlWriter.Create(path, settings))
            {
                dataContractSerializer.WriteObject(writer, obj);
            }
        }

        /// <summary>
        /// Writes serialized binary object into file
        /// </summary>
        /// <typeparam name="T">Object type</typeparam>
        /// <param name="obj">Object valu</param>
        /// <param name="path">File path</param>
        /// <exception cref="ArgumentException">If input file name is invalid</exception>
        public static void BinaryWrite<T>(T obj, string path)
        {
            if (!Path.HasExtension(path))
                throw new ArgumentException("Invalid file name");

            SqlBytes sertilizedObject = BinarySerialize(obj);
            using (StreamWriter writer = new StreamWriter(path))
            {
                writer.Write(sertilizedObject.Buffer);
            }
        }

        #endregion

        #region Reader

        /// <summary>
        /// Reads object from xml formated file
        /// </summary>
        /// <typeparam name="T">Object type</typeparam>
        /// <param name="path">File path</param>
        /// <returns>Object value</returns>
        /// <exception cref="IOException">If file doesn't exist</exception>
        public static T XmlRead<T>(string path)
        {
            if (!File.Exists(path))
                throw new IOException("File doesn't exist");

            using (XmlReader reader = XmlReader.Create(path))
            {
                return XmlDeserialize<T>(new SqlXml(reader));
            }
        }

        /// <summary>
        /// Reads object from binare formated file
        /// </summary>
        /// <typeparam name="T">Object type</typeparam>
        /// <param name="path">File path</param>
        /// <returns>Object value</returns>
        /// <exception cref="IOException">If file doesn't exist</exception>
        public static T BinaryRead<T>(string path)
        {
            if (!File.Exists(path))
                throw new IOException("File doesn't exist");

            using (StreamReader reader = new StreamReader(path))
            {
                return BinaryDeserialize<T>(new SqlBytes(new MemoryStream(Encoding.UTF8.GetBytes(reader.ReadToEnd()))));
            }
        }

        #endregion
    }
}
